package com.jsjvm.client.execution.objrepr.java;

import java.util.HashMap;
import java.util.Map.Entry;

import com.jsjvm.client.classparser.JClass;
import com.jsjvm.client.classparser.JMember.JField;
import com.jsjvm.client.classparser.JMember.JMethod;
import com.jsjvm.client.classparser.jconstants.JMemberConstant;
import com.jsjvm.client.dbg.ObjectIDManager.SimpleIDIdentifiable;
import com.jsjvm.client.execution.JClassLoader;
import com.jsjvm.client.execution.JClassLoader.JClassNotLoadedException;
import com.jsjvm.client.execution.objrepr.ObjectRepr;

/**
 * Unoptimized Java-based runtime representation for objects. Hash lookups can
 * be replaced by array indexing by some pre-processing of code.
 * 
 * This representation is not usefull to use GWT JSNI.
 * 
 * @author ctalau
 */
public class JavaObjectRepr extends SimpleIDIdentifiable implements ObjectRepr  {
	private static HashMap<String, JavaObjectRepr> cache = new HashMap<String, JavaObjectRepr>();

	private HashMap<String, Object> objTmpl = new HashMap<String, Object>();
	private HashMap<String, JMethod> dynamicDispatchTable = new HashMap<String, JMethod>();
	private HashMap<String, JMethod> staticDispatchTable = new HashMap<String, JMethod>();
	protected JClass jc;

	@Override
	public Object getField(JMemberConstant fld) {
		return objTmpl.get(fld.getFullName());
	}

	@Override
	public void putField(JMemberConstant fld, Object value) {
		objTmpl.put(fld.getFullName(), value);
	}

	@Override
	public JMethod dispatchMethod(JMemberConstant cm) {
		return dynamicDispatchTable.get(cm.getMemberName());
	}

	@Override
	public JMethod dispatchSuperMethod(JMemberConstant cm)
			throws JClassNotLoadedException {
		JavaObjectRepr superObj = getCachedJavaObject(jc.getSuperClass());
		return superObj.dispatchMethod(cm);
	}

	@Override
	public JMethod dispatchNonVirtualMethod(JMemberConstant cm)
			throws JClassNotLoadedException {
		JClassLoader jcl = JClassLoader.getInstance();
		JClass jc = jcl.getClassByConstant(cm.getClassConstant());
		return getCachedJavaObject(jc).staticDispatchTable.get(cm.getMemberName());
	}

	@Override
	public JClass getJClass() {
		return jc;
	}

	/**
	 * Create a new object of that class by caching a template object.
	 */
	public static JavaObjectRepr newJavaObjectRepr(JClass jc) {
		JavaObjectRepr tmpl = getCachedJavaObject(jc);
		JavaObjectRepr ret = new JavaObjectRepr();

		ret.objTmpl = new HashMap<String, Object>(tmpl.objTmpl);
		ret.dynamicDispatchTable = tmpl.dynamicDispatchTable;
		ret.staticDispatchTable = tmpl.staticDispatchTable;
		ret.jc = tmpl.jc;

		return ret;
	}

	private static JavaObjectRepr getCachedJavaObject(JClass jc) {
		JavaObjectRepr o = cache.get(jc.getName());
		if (o == null) {
			o = new JavaObjectRepr();
			o.putMembers(jc);
			o.jc = jc;
			cache.put(jc.getName(), o);
		}
		return o;
	}

	/**
	 * Build template object, static and dynamic dispatch table
	 */
	protected void putMembers(JClass c) {
		putMembers_(c);

		for (JMethod m : c.getMethods()) {
			if (m.isStatic())
				continue;
			this.staticDispatchTable.put(m.getMemberName(), m);
		}
	}

	/**
	 * Build the template object and the dynamic dispatch table recursively
	 */
	private void putMembers_(JClass c) {
		for (JField f : c.getFields()) {
			if (f.isStatic())
				continue;

			this.objTmpl.put(f.getFullName(), f.getInitValue());
		}

		for (JMethod m : c.getMethods()) {
			if (m.isStatic())
				continue;

			if (this.dynamicDispatchTable.get(m.getMemberName()) == null) {
				this.dynamicDispatchTable.put(m.getMemberName(), m);
			}
			// else - the method is overriden
		}

		if (c.getSuperClass() != null)
			putMembers_(c.getSuperClass());
	}

	@Override
	public String toString() {
		String ret = this.jc.getName() + "-{";
		for (Entry<String, Object> e : this.objTmpl.entrySet()) {
			ret += e.getKey() + " : " + e.getValue().toString() + ",";
		}
		ret += "}";
		return ret;
	}

}
